//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#define _ERRNO_
#include <ddk.h>
#include <errno.h>
#include "define.h"
#include "control.h"

static SERIAL_ErrNo serial_write(
    io_handle_t handle,
    const void *buf,
    Int32 *len
);

static SERIAL_ErrNo serial_read(
    io_handle_t handle,
    void *buf,
    Int32 *len
);

static SERIAL_ErrNo serial_get_config(
    io_handle_t handle,
    Int32 key,
    void *buf,
    Int32 *len
);

static SERIAL_ErrNo serial_set_config(
    io_handle_t handle,
    Int32 key,
    const void *buf,
    Int32 *len
);

static SERIAL_ErrNo serial_wait_event(
    io_handle_t handle,
    int *mask
);

DEVIO_TABLE(
    g_serial_devio,
    serial_write,
    serial_read,
    serial_get_config,
    serial_set_config,
    serial_wait_event);

// serial callbacks are as follows
static void serial_init(serial_channel *chan);
static void serial_xmt_char(serial_channel *chan);
static void serial_rcv_char(serial_channel *chan, unsigned char c);
static void serial_indicate_status(
    serial_channel *chan,
    serial_line_status_t *s
);

static rcv_req_reply_t serial_data_rcv_req(
    serial_channel *chan, int avail,
    int* space_avail,
    unsigned char** space
);

static void serial_data_rcv_done(
    serial_channel *chan,
    int chars_rcvd
);

static xmt_req_reply_t serial_data_xmt_req(
    serial_channel *chan, int space,
    int* chars_avail,
    unsigned char** chars);

static void serial_data_xmt_done(
    serial_channel *chan,
    int chars_sent
);

SERIAL_CALLBACKS(
    g_serial_callbacks,
    serial_init,
    serial_xmt_char,
    serial_rcv_char,
    serial_data_rcv_req,
    serial_data_rcv_done,
    serial_data_xmt_req,
    serial_data_xmt_done,
    serial_indicate_status);

const static Int32 s_baudidx[] = {
    0,
    50,
    75,
    110,
    134,
    150,
    200,
    300,
    600,
    1200,
    1800,
    2400,
    3600,
    4800,
    7200,
    9600,
    14400,
    19200,
    38400,
    57600,
    115200,
    230400,
    380400,
    460800,
    921600,
};

/* cbuf functions:
    ---g******p----
    ***p------g****
*/
static __inline__ int
cbuf_nb(cbuf_t *cbuf)
{
    return (cbuf->put - cbuf->get + cbuf->len) % cbuf->len;
}

static __inline__ int
cbuf_len(cbuf_t *cbuf)
{
    return cbuf->len;
}

static __inline__ bool
cbuf_empty(cbuf_t *cbuf)
{
    if (cbuf->put == cbuf->get) {
        return true;
    }
    else {
        return false;
    }
}

static __inline__ bool
cbuf_get(cbuf_t *cbuf, unsigned char *c)
{
    assert(!cbuf_empty(cbuf));

    *c = cbuf->data[cbuf->get];

    return true;
}

static __inline__ bool
cbuf_full(cbuf_t *cbuf)
{
    if ((cbuf->put + 1) % cbuf->len == cbuf->get) {
        return true;
    }
    else {
        return false;
    }
}

static __inline__ bool
cbuf_en(cbuf_t *cbuf, unsigned char c)
{
    assert(!cbuf_full(cbuf) && "buffer full");

    /* sure that the buffer isn't full */
    cbuf->data[cbuf->put] = c;
    cbuf->put = (cbuf->put + 1) % cbuf->len;

    return true;
}

static __inline__ bool
cbuf_de(cbuf_t *cbuf)
{
    assert(!cbuf_empty(cbuf) && "buffer empty");

    cbuf->get = (cbuf->get + 1) % cbuf->len;

    return true;
}

// ---------------------------------------------------------------------------

static __inline__ uint_t
calculate_serial_timeout(
    int baud, uint_t nb)
{
    uint_t tmp = (nb << 10) / (s_baudidx[baud] >> 3);
    tmp = ((tmp > 0) ? tmp : 3) + 10;
    return DzMillisecondsToTicks(tmp);
}

// ---------------------------------------------------------------------------

static __inline__ void
throttle_tx(serial_channel *chan)
{
    chan->flow_desc.flags |= SERIAL_FLOW_OUT_THROTTLED;
    // the throttling itself occurs in the serial_xmt_char() callback
}

static __inline__ void
restart_tx(serial_channel *chan)
{
    serial_funs *funs = chan->funs;

    chan->flow_desc.flags &= ~SERIAL_FLOW_OUT_THROTTLED;

    if (!cbuf_empty(&chan->out_cbuf))
        (funs->start_xmit)(chan);
}

static __inline__ void
throttle_rx(serial_channel *chan, boolean force)
{
    serial_funs *funs = chan->funs;

    chan->flow_desc.flags |= SERIAL_FLOW_IN_THROTTLED;

    if (chan->flow_desc.flowctrl_software) {
        // send an xoff
        if (force || chan->config.flags & NUM_SERIAL_FLOW_XONXOFF_RX) {
            chan->flow_desc.xchar = DAT_IO_SERIAL_FLOW_CONTROL_XOFF_CHAR;
            // Make sure xmit is running so we can send it
            (funs->start_xmit)(chan);
        }
    }
    else {
        Int32 i = 1;
        Int32 len = sizeof(i);

        // set hardware flow control - don't care if it fails
        if (force || (chan->config.flags & NUM_SERIAL_FLOW_RTSCTS_RX) ||
            (chan->config.flags & NUM_SERIAL_FLOW_DSRDTR_RX))
            (funs->set_config)(chan,
                               IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE,
                               &i, &len);
    }
}

/**
 * for flow control
 *
 * @param chan
 * @param force
 *
 * @return
 */
static __inline__ void
restart_rx(serial_channel *chan, boolean force)
{
    serial_funs *funs = chan->funs;

    chan->flow_desc.flags &= ~SERIAL_FLOW_IN_THROTTLED;

    if (chan->flow_desc.flowctrl_software) {
        // send an xon
        if (force || chan->config.flags & NUM_SERIAL_FLOW_XONXOFF_RX) {
            chan->flow_desc.xchar = DAT_IO_SERIAL_FLOW_CONTROL_XON_CHAR;
            (funs->start_xmit)(chan);  // Make sure xmit is running so we
                                       // can send it
        }
    }
    else {
        Int32 i=0;
        Int32 len = sizeof(i);

        // set hardware flow control - don't care if it fails
        if (force || (chan->config.flags & NUM_SERIAL_FLOW_RTSCTS_RX) ||
            (chan->config.flags & NUM_SERIAL_FLOW_DSRDTR_RX))
            (funs->set_config)(chan,
                               IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE,
                               &i, &len);
    }
}

static void serial_reset_timeout(
    serial_channel *chan)
{
    // unit in tick
    chan->extension.timeouts.ReadIntervalTimeout =
        DzMillisecondsToTicks(100);
    chan->extension.timeouts.ReadTotalTimeoutMultiplier =
        DzMillisecondsToTicks(20);
    chan->extension.timeouts.ReadTotalTimeoutConstant =
        DzMillisecondsToTicks(2000);
    chan->extension.timeouts.WriteTotalTimeoutMultiplier =
        DzMillisecondsToTicks(20);
    chan->extension.timeouts.WriteTotalTimeoutConstant =
        DzMillisecondsToTicks(2000);
}

extern bool serial_devtab_res_init(
    devtab_entry * tab,
    unsigned int port_base,
    int irq);

bool serial_device_entry(
    devtab_entry * tab,
    int port_base,
    int irq)
{
    bool res;
    res = serial_devtab_res_init(
        tab,
        port_base,
        irq);
    if(!res) { return false; }
    serial_channel *chan = (serial_channel *)tab->priv;
    res = chan->funs->close(chan);
    if(!res) { return false; }
    serial_reset_timeout(chan);
    return res;
}

// ---------------------------------------------------------------------------

static void
serial_init(serial_channel *chan) // initialize common resources...
{
    if (chan->bufs_init) { return; }

    chan->comm_event.mask = 0;
    chan->comm_event.value = 0;
    chan->comm_event.waiting = 0;
    _drv_event_init(&chan->comm_event.wait);

    if (chan->out_cbuf.len != 0) {
#ifdef SERIALDBG_IO_INIT
        kprintf("Set output buffer - buf: 0x%X len: %d\n",
                chan->out_cbuf.data, chan->out_cbuf.len);
#endif

        chan->out_cbuf.waiting = false;
        chan->out_cbuf.abort = false;
        chan->out_cbuf.pending = 0;
        chan->out_cbuf.low_water = chan->out_cbuf.len / 4;

        _mutex_init(&chan->out_cbuf.lock);
        _drv_event_init(&chan->out_cbuf.wait);
    }

    if (chan->in_cbuf.len != 0) {
        cbuf_t *cbuf = &chan->in_cbuf;

#ifdef SERIALDBG_IO_INIT
        kprintf("Set input buffer - buf: 0x%X len: %d\n",
                cbuf->data, cbuf->len);
#endif
        cbuf->waiting = false;
        cbuf->abort = false;
        if (chan->support_flow_control) {
            cbuf->low_water =
                (SERIALNUM_IO_SERIAL_FLOW_CONTROL_LOW_WATER_PERCENT
                    * cbuf->len) / 100;
            cbuf->high_water =
                (SERIALNUM_IO_SERIAL_FLOW_CONTROL_HIGH_WATER_PERCENT
                    * cbuf->len) / 100;

            if (chan->flow_desc.flowctrl_software) {
                // But make sure it is at least 35 below buffer size, to allow
                // for 16 byte fifos, twice, plus some latency before s/w flow
                // control can kick in. This doesn't apply to h/w flow control
                // as it is near-instaneous
                if ((cbuf->len - cbuf->high_water) < 35)
                    cbuf->high_water = cbuf->len - 35;
                // and just in case...
                if (cbuf->high_water <= 0) {
                    cbuf->high_water = 1;
                }
                if (cbuf->low_water > cbuf->high_water) {
                    cbuf->low_water = cbuf->high_water;
                }
            }
        }

        _mutex_init(&cbuf->lock);
        _drv_event_init(&cbuf->wait);
    }

    chan->status_callback = NULL;
    chan->bufs_init = true;
}

static __inline__ SERIAL_ErrNo
serial_write_aid(
    cbuf_t *cbuf,
    serial_channel *chan,
    Int32 timeout
    )
{
    SERIAL_ErrNo res;
    int retry_times;
    uint_t total_timeout = 0;

    res = ENOERR;
    retry_times = 0;
    WaitResult wr;
    while (!cbuf_empty(cbuf) && (Int32)total_timeout < timeout) {
        uint_t subtimeout = calculate_serial_timeout(
            chan->config.baud, cbuf_nb(cbuf));
        total_timeout += subtimeout;

        cbuf->waiting = true;

        if (chan->support_flow_control) {
            // Make sure xmit is running
            (chan->funs->stop_xmit)(chan);
            DzDelay(500);
            (chan->funs->start_xmit)(chan);
        }

        _drv_event_try_wait(&cbuf->wait, &wr, subtimeout);
        cbuf->waiting = false;
        _drv_event_clear(&cbuf->wait);
        if (WaitResult_TimedOut == wr) { /* interrupt is lost? */
            SERIAL_LOG1("SERIAL_LOG: serial_write_aid--> timeout!\n");
            res = -ETIMEDOUT;
        }
    }

    return res;
}

// ---------------------------------------------------------------------------
static SERIAL_ErrNo
serial_write(io_handle_t handle, const void *_buf, Int32 *len)
{
    devtab_entry *t = (devtab_entry *)handle;
    serial_channel *chan = (serial_channel *)t->priv;
    Int32 size = *len;
    UInt8 *buf = (UInt8 *)_buf;
    cbuf_t *cbuf = &chan->out_cbuf;
    SERIAL_ErrNo res = ENOERR;

    // add this for prevent some exceptions
    if (buf == NULL || len == 0) {
        return -EINVAL;
    }

    WaitResult wr;
    _drv_mutex_lock(&cbuf->lock, &wr);

    while (size > 0) {
        if (cbuf_full(cbuf)) {
            // Buffer full - wait for space

            cbuf->pending += size; // Have this much more to send [eventually]

            SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
            uint_t totaltimeouts =
                timeouts.WriteTotalTimeoutConstant +
                cbuf_nb(cbuf) * timeouts.WriteTotalTimeoutMultiplier;
            res = serial_write_aid(
                cbuf,
                chan,
                totaltimeouts);
            if (cbuf_empty(cbuf)) {
                cbuf->pending -= cbuf_len(cbuf);
            }
            else {
                cbuf->pending -= (cbuf_len(cbuf) - cbuf_nb(cbuf));
                break;
            }
        }
        else {
            cbuf_en(cbuf, *buf++);
            size--;  // Only count if actually sent!
        }
    }

    if (!cbuf_empty(cbuf)) {
        SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
        uint_t totaltimeouts =
            timeouts.WriteTotalTimeoutConstant +
            cbuf_nb(cbuf) * timeouts.WriteTotalTimeoutMultiplier;
        res = serial_write_aid(
            cbuf,
            chan,
            totaltimeouts);
        cbuf->pending -= (cbuf_len(cbuf) - cbuf_nb(cbuf));
    }

    if (cbuf_empty(cbuf)) {
        *len -= size;   // number of characters actually sent
        res = ENOERR;
    }
    else {
        *len -= (size + cbuf_nb(cbuf));
        cbuf->get = cbuf->put = 0;      // Empties queue!
        (chan->funs->stop_xmit)(chan);  // Done with transmit
        res = -ETIMEDOUT;
    }

    _drv_mutex_unlock(&cbuf->lock);

    return res;
}

// ---------------------------------------------------------------------------

static void CDECL serial_cbuf_abort(
    void * data)
{
    cbuf_t *cbuf = (cbuf_t *) data;
    cbuf->abort = true;
    SERIAL_LOG1("SERIAL_LOG: serial_cbuf_abort--> true\n");
}

static int __inline__ serial_fecth_inbuf(
    serial_channel *chan,
    UInt8 *buf,
    UInt32 len)
{
    cbuf_t *cbuf = &chan->in_cbuf;
    UInt32 size = 0;

    while (size < len) {
        if (!cbuf_empty(cbuf)) {
            if (chan->support_flow_control) {
                if ((cbuf_nb(cbuf) <= cbuf->low_water) &&
                    (chan->flow_desc.flags & SERIAL_FLOW_IN_THROTTLED)) {
                    restart_rx(chan, false);
                }
            }
            cbuf_get(cbuf, buf++);
            cbuf_de(cbuf);
            size++;
        }
        else {
            break;
        }
    }
    return size;
}

static SERIAL_ErrNo
serial_read(io_handle_t handle, void *_buf, Int32 *len)
{
    devtab_entry *t = (devtab_entry *)handle;
    serial_channel *chan = (serial_channel *)t->priv;
    UInt8 *buf = (UInt8 *)_buf;
    UInt32 size = 0, residual = *len;
    cbuf_t *cbuf = &chan->in_cbuf;
    SERIAL_ErrNo res = ENOERR;
    SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
    WaitResult wr;

    if (buf == NULL || len == 0) {
        return -EINVAL;
    }

    DzTimer timer(serial_cbuf_abort, &cbuf);


    _drv_mutex_lock(&cbuf->lock, &wr);

    cbuf->abort = false;
    uint_t totaltimeouts =
        timeouts.ReadTotalTimeoutConstant +
        (residual * timeouts.ReadTotalTimeoutMultiplier);
    totaltimeouts = (totaltimeouts == 0) ? (uint_t) INFINITE : totaltimeouts;
//    DzTimer timer(totaltimeouts, serial_cbuf_abort, &cbuf);

    timer.Set(totaltimeouts);
    timer.Start();
    for (;;) {
        UInt32 tsize;
        tsize = serial_fecth_inbuf(chan, buf, residual);
        size += tsize;
        buf += tsize;
        residual -= tsize;
        SERIAL_LOG5(
            "SERIAL_LOG: serial_read count: size:%d, residual:%d, tsize:%d," \
            "*len:%d\n", size, residual, tsize, *len);
        if (residual == 0) {
            *len = size;
            SERIAL_LOG1("SERIAL_LOG: serial_read completed--> OK\n");
            break;
        }
        if (cbuf->abort) {
            *len = size;
            break;
        }

        if (size == 0) {
            cbuf->waiting = true;
            _drv_event_try_wait(&cbuf->wait, &wr, totaltimeouts);
            cbuf->waiting = false;
            _drv_event_clear(&cbuf->wait);
            if (wr == WaitResult_TimedOut) {
                cbuf->abort = true;
                SERIAL_LOG1(
                    "SERIAL_LOG: serial_read totaltimeouts--> TimedOut\n");
            }
        }
        else {
            cbuf->waiting = true;
            _drv_event_try_wait(&cbuf->wait, &wr,
                            timeouts.ReadIntervalTimeout);
            cbuf->waiting = false;
            _drv_event_clear(&cbuf->wait);
            if (wr == WaitResult_TimedOut) {
                cbuf->abort = true;
                SERIAL_LOG1(
                    "SERIAL_LOG: serial_read ReadIntervalTimeout-->"
                    " TimedOut\n");
            }
        }
    }
    cbuf->abort = false;
    timer.Cancel();
    _drv_mutex_unlock(&cbuf->lock);

    return (res);
}

// ---------------------------------------------------------------------------

static SERIAL_ErrNo
serial_get_config(
    io_handle_t handle,
    Int32 key,
    void *xbuf,
    Int32 *len
    )
{
    devtab_entry *t = (devtab_entry *)handle;
    serial_channel *chan = (serial_channel *)t->priv;
    SERIAL_INFO *buf = (SERIAL_INFO *)xbuf;
    SERIAL_ErrNo res = ENOERR;
    cbuf_t *out_cbuf = &chan->out_cbuf;
    cbuf_t *in_cbuf = &chan->in_cbuf;
    serial_funs *funs = chan->funs;
    WaitResult wr;

    switch (key) {
        case IO_GET_CONFIG_SERIAL_TIMEOUT :{
            SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
            if (*len < (Int32)sizeof(SERIAL_TIMEOUTS)) {
                return -EINVAL;
            }
            timeouts.ReadIntervalTimeout =
                DzTicksToMilliseconds(timeouts.ReadIntervalTimeout);
            timeouts.ReadTotalTimeoutMultiplier =
                DzTicksToMilliseconds(timeouts.ReadTotalTimeoutMultiplier);
            timeouts.ReadTotalTimeoutConstant =
                DzTicksToMilliseconds(timeouts.ReadTotalTimeoutConstant);
            timeouts.WriteTotalTimeoutMultiplier =
                DzTicksToMilliseconds(timeouts.WriteTotalTimeoutMultiplier);
            timeouts.WriteTotalTimeoutConstant =
                DzTicksToMilliseconds(timeouts.WriteTotalTimeoutConstant);
            (* ((SERIAL_TIMEOUTS *)xbuf)) = timeouts;
        }
        break;

        case IO_GET_CONFIG_SERIAL_INFO:
            if (*len < (Int32)sizeof(SERIAL_INFO)) {
                return (-EINVAL);
            }
            *buf = chan->config;
            *len = sizeof(chan->config);
            break;

        case IO_GET_CONFIG_SERIAL_BUFFER_INFO:
        // return rx/tx buffer sizes and counts
        {
            serial_buf_info_t *p;
            if (*len < (Int32)sizeof(serial_buf_info_t)) {
                return (-EINVAL);
            }

            *len = sizeof(serial_buf_info_t);
            p = (serial_buf_info_t *)xbuf;

            p->rx_bufsize = in_cbuf->len;
            if (p->rx_bufsize) {
                p->rx_count = cbuf_nb(in_cbuf);
            }
            else {
                p->rx_count = 0;
            }

            p->tx_bufsize = out_cbuf->len;
            if (p->tx_bufsize) {
                p->tx_count = cbuf_nb(out_cbuf);
            }
            else {
                p->tx_count = 0;
            }
        }
        break;

        case IO_GET_CONFIG_SERIAL_OUTPUT_DRAIN:
        // Wait for any pending output to complete
            if (out_cbuf->len == 0) { break; } // Nothing to do if not buffered

            _drv_mutex_lock(&out_cbuf->lock, &wr);  // Stop any further

            // output processing
            while (out_cbuf->pending || !cbuf_empty(out_cbuf)) {
                out_cbuf->waiting = true;
                _drv_event_try_wait(&out_cbuf->wait, &wr, 1000);
                out_cbuf->waiting = false;
                _drv_event_clear(&out_cbuf->wait);
                if (WaitResult_OK != wr) {
                    res = -EINTR;
                }
                out_cbuf->waiting = false;
            }

            _drv_mutex_unlock(&out_cbuf->lock);

            break;

        case IO_GET_CONFIG_SERIAL_INPUT_FLUSH:
        // Flush any buffered input
            if (in_cbuf->len == 0) { break; } // Nothing to do if not buffered

            _drv_mutex_lock(&in_cbuf->lock, &wr);  // Stop any further input

            // processing
            if (in_cbuf->waiting) {
                in_cbuf->abort = true;
                _drv_event_notify(&in_cbuf->wait);
                in_cbuf->waiting = false;
            }
            in_cbuf->get = in_cbuf->put = 0;  // Flush buffered input

            _drv_mutex_unlock(&in_cbuf->lock);

            break;

        case IO_GET_CONFIG_SERIAL_ABORT:
        // Abort any outstanding I/O, including blocked reads
        // Caution - assumed to be called from 'timeout' (i.e. DSR) code
            if (in_cbuf->len != 0 && in_cbuf->waiting) {
                in_cbuf->abort = true;
                _drv_event_notify(&in_cbuf->wait);
            }
            if (out_cbuf->len != 0 && out_cbuf->waiting) {
                out_cbuf->abort = true;
                _drv_event_notify(&out_cbuf->wait);
            }
            if (chan->comm_event.mask != 0) {
                chan->comm_event.mask = 0;
                chan->comm_event.waiting = 0;
                _drv_event_notify(&chan->comm_event.wait);
            }
            break;

        case IO_GET_CONFIG_SERIAL_OUTPUT_FLUSH:
        // Throw away any pending output
            if (out_cbuf->len == 0) { break; } // Nothing to do if not buffered

            _drv_mutex_lock(&out_cbuf->lock, &wr);  // Stop any further

            // output processing
            if (!cbuf_empty(out_cbuf)) {
                out_cbuf->get = out_cbuf->put = 0;  // Empties queue!
                (funs->stop_xmit)(chan);  // Done with transmit
            }
            if (out_cbuf->waiting) {
                out_cbuf->abort = true;
                _drv_event_notify(&out_cbuf->wait);
                out_cbuf->waiting = false;
            }

            _drv_mutex_unlock(&out_cbuf->lock);

            break;

        case IO_GET_CONFIG_READ_BLOCKING:{
            if (*len < (Int32)sizeof(UInt32)) {
                return (-EINVAL);
            }
            SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
            if (timeouts.ReadIntervalTimeout == (uint_t) INFINITE &&
                timeouts.ReadTotalTimeoutMultiplier == 0 &&
                timeouts.ReadTotalTimeoutConstant == 0) {
                *(UInt32*)xbuf = true;
            }
            else {
                *(UInt32*)xbuf = false;
            }
        }
        break;

        case IO_GET_CONFIG_WRITE_BLOCKING:
            return (-EINVAL);
            break;

        case IO_GET_CONFIG_SERIAL_WAITEVENT_MASK:{
            if (*len < (Int32)sizeof(chan->comm_event.mask)) {
                return (-EINVAL);
            }
            *(int *) xbuf = chan->comm_event.mask;
        }
        break;

        case IO_GET_CONFIG_SERIAL_MODEM_STATUS:
        // pass down to lower layers
            return (funs->set_config)(
            chan,
            IO_GET_CONFIG_SERIAL_MODEM_STATUS,
            xbuf,
            len);
            break;

        default:
        // pass down to lower layers
        return(funs->set_config)(chan, key, xbuf, len);
    }

    return (res);
}

// ---------------------------------------------------------------------------

static SERIAL_ErrNo
serial_set_config(
    io_handle_t handle,
    Int32 key,
    const void *xbuf,
    Int32 *len)
{
    SERIAL_ErrNo res = ENOERR;
    devtab_entry *t = (devtab_entry *)handle;
    serial_channel *chan = (serial_channel *)t->priv;
    cbuf_t *out_cbuf = &chan->out_cbuf;
    cbuf_t *in_cbuf = &chan->in_cbuf;
    serial_funs *funs = chan->funs;
    WaitResult wr;

    switch (key) {
        case IO_SET_CONFIG_SERIAL_OPEN :
        // enabled tx & rx interrupt
            _drv_mutex_lock(&in_cbuf->lock, &wr);
            _drv_mutex_lock(&out_cbuf->lock, &wr);
            funs->open(chan);
            _drv_mutex_unlock(&in_cbuf->lock);
            _drv_mutex_unlock(&out_cbuf->lock);
            serial_reset_timeout(chan);
            break;

        case IO_SET_CONFIG_SERIAL_CLOSE :
        // disable tx & rx interrupt
            _drv_mutex_lock(&in_cbuf->lock, &wr);
            _drv_mutex_lock(&out_cbuf->lock, &wr);
            funs->close(chan);
            _drv_mutex_unlock(&in_cbuf->lock);
            _drv_mutex_unlock(&out_cbuf->lock);
            break;

        case IO_SET_CONFIG_SERIAL_TIMEOUT :{
            SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;

            if (*len < (Int32)sizeof(SERIAL_TIMEOUTS)) {
                return -EINVAL;
            }

            timeouts = (* ((SERIAL_TIMEOUTS *)xbuf));
            timeouts.ReadIntervalTimeout =
                DzMillisecondsToTicks(timeouts.ReadIntervalTimeout);
            timeouts.ReadTotalTimeoutMultiplier =
                DzMillisecondsToTicks(timeouts.ReadTotalTimeoutMultiplier);
            timeouts.ReadTotalTimeoutConstant =
                DzMillisecondsToTicks(timeouts.ReadTotalTimeoutConstant);
            timeouts.WriteTotalTimeoutMultiplier =
                DzMillisecondsToTicks(timeouts.WriteTotalTimeoutMultiplier);
            timeouts.WriteTotalTimeoutConstant =
                DzMillisecondsToTicks(timeouts.WriteTotalTimeoutConstant);
        }
        break;

        case IO_SET_CONFIG_SERIAL_BUFFER_INFO :{
            // only buffer length members if 'serial_buf_info_t' are used!
            serial_buf_info_t *p;
            unsigned char *pdata;

            if (*len < (Int32)sizeof(serial_buf_info_t)) {
                return (-EINVAL);
            }

            *len = sizeof(serial_buf_info_t);
            p = (serial_buf_info_t *)xbuf;

            if (p->rx_bufsize > (unsigned long) in_cbuf->len) {

                _drv_mutex_lock(&in_cbuf->lock, &wr); // Stop any further input

                // Flush any buffered input
                // processing
                if (in_cbuf->len > 0) {
                    in_cbuf->abort = true;
                    _drv_event_notify(&in_cbuf->wait);
                }

                in_cbuf->abort = false;
                free(in_cbuf->data);

                pdata = (unsigned char *) malloc(p->rx_bufsize);
                if (pdata) {
                    in_cbuf->data = pdata;
                    in_cbuf->len = p->rx_bufsize;
                    memset(in_cbuf->data, 0, in_cbuf->len);
                    in_cbuf->get = 0;
                    in_cbuf->put = 0;
                }

                _drv_mutex_unlock(&in_cbuf->lock);
            }

            if (p->tx_bufsize > (unsigned long) out_cbuf->len) {
                _drv_mutex_lock(&out_cbuf->lock, &wr);

                // output processing
                if (!cbuf_empty(out_cbuf)) {
                    (funs->stop_xmit)(chan);  // Done with transmit
                }
                out_cbuf->abort = true;
                _drv_event_notify(&out_cbuf->wait);

                out_cbuf->abort = false;
                free(out_cbuf->data);

                pdata = (unsigned char *) malloc(p->tx_bufsize);
                if (pdata) {
                    out_cbuf->data = pdata;
                    out_cbuf->len = p->tx_bufsize;
                    memset(out_cbuf->data, 0, out_cbuf->len);
                    out_cbuf->get = 0;
                    out_cbuf->put = 0;
                }

                _drv_mutex_unlock(&out_cbuf->lock);
            }
        }

        break;

        case IO_SET_CONFIG_READ_BLOCKING:{
            bool blocking;
            if (*len < (Int32)sizeof(UInt32) || 0 == in_cbuf->len) {
                return (-EINVAL);
            }
            blocking = (* ((bool *) xbuf)) ? true : false;
            if (blocking) {
                SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
                timeouts.ReadIntervalTimeout = (uint_t)INFINITE;
                timeouts.ReadTotalTimeoutMultiplier = 0;
                timeouts.ReadTotalTimeoutConstant = 0; // tick
            }
            else {
                SERIAL_TIMEOUTS &timeouts = chan->extension.timeouts;
                timeouts.ReadIntervalTimeout = 1;
                timeouts.ReadTotalTimeoutMultiplier = 0;
                timeouts.ReadTotalTimeoutConstant = 5; // tick
            }
        }
        break;

        case IO_SET_CONFIG_WRITE_BLOCKING:{
            return (-EINVAL);
        }
        break;

        case IO_SET_CONFIG_SERIAL_FLOW_CONTROL_METHOD:{
            UInt32 *f = (UInt32 *)xbuf;

            if (*len < (Int32)sizeof(*f)) {
                return (-EINVAL);
            }

            /* initialize */
            chan->config.flags &= ~(NUM_SERIAL_FLOW_XONXOFF_RX|
                                    NUM_SERIAL_FLOW_XONXOFF_TX|
                                    NUM_SERIAL_FLOW_RTSCTS_RX|
                                    NUM_SERIAL_FLOW_RTSCTS_TX|
                                    NUM_SERIAL_FLOW_DSRDTR_RX|
                                    NUM_SERIAL_FLOW_DSRDTR_TX);

            if (chan->support_flow_control) {
                if (chan->flow_desc.flowctrl_software) {
                    chan->config.flags |= (*f & (
                                             NUM_SERIAL_FLOW_XONXOFF_RX|
                                             NUM_SERIAL_FLOW_XONXOFF_TX));
                }
                else {
                    chan->config.flags |= (*f & (
                                         NUM_SERIAL_FLOW_RTSCTS_RX|
                                         NUM_SERIAL_FLOW_RTSCTS_TX|
                                         NUM_SERIAL_FLOW_DSRDTR_RX|
                                         NUM_SERIAL_FLOW_DSRDTR_TX|
                                         0));
                }
            }
        }
        break;

        case IO_SET_CONFIG_SERIAL_FLOW_CONTROL_FORCE:{
            UInt32 *f = (UInt32 *)xbuf;

            if (*len < (Int32)sizeof(*f)) {
                return (-EINVAL);
            }

            switch (*f) {
                case NUM_SERIAL_FLOW_THROTTLE_RX:
                    throttle_rx(chan, true);
                    break;
                case NUM_SERIAL_FLOW_RESTART_RX:
                    restart_rx(chan, true);
                    break;
                case NUM_SERIAL_FLOW_THROTTLE_TX:
                    throttle_tx(chan);
                    break;
                case NUM_SERIAL_FLOW_RESTART_TX:
                    restart_tx(chan);
                    break;
                default:
                    res = -EINVAL;
                    break;
            }
        }
        break;

        case IO_SET_CONFIG_SERIAL_STATUS_CALLBACK:{
            serial_line_status_callback_fn_t newfn;
            SERIAL_ADDRWORD newpriv;
            serial_line_status_callback_t *tmp =
                (serial_line_status_callback_t *)xbuf;

            if (*len < (Int32)sizeof(*tmp)) {
                return (-EINVAL);
            }

            newfn = tmp->fn;
            newpriv = tmp->priv;

            // prevent callbacks while we do this

            // store old callbacks in same structure
            tmp->fn = chan->status_callback;
            tmp->priv = chan->status_callback_priv;
            chan->status_callback = newfn;
            chan->status_callback_priv = newpriv;

            *len = sizeof(*tmp);
        }
        break;

        case IO_SET_CONFIG_SERIAL_WAITEVENT_MASK:{
            int *mask;

            if (*len < (Int32)sizeof(*mask)) {
                return (-EINVAL);
            }
            mask = (int *) xbuf;
            chan->comm_event.mask = *mask;
            *len = sizeof(*mask);
        }
        break;

        default:
            // pass down to lower layers
            return(funs->set_config)(chan, key, xbuf, len);
    }
    return (res);
}

static SERIAL_ErrNo
serial_wait_event(
    io_handle_t handle,
    int *mask)
{
    devtab_entry *t = (devtab_entry *)handle;
    serial_channel *chan = (serial_channel *)t->priv;
    cbuf_t *cbuf = &chan->in_cbuf;
    if (mask == NULL) { return -EINVAL; }

    if ((chan->comm_event.mask & NUM_EV_RXCHAR) &&
        !cbuf_empty(cbuf)) {
        *mask = NUM_EV_RXCHAR;
    }
    else {
        WaitResult wr;
        chan->comm_event.waiting = chan->comm_event.mask;
        _drv_event_wait(&chan->comm_event.wait, &wr);
        _drv_event_clear(&chan->comm_event.wait);
        if (WaitResult_Interrupted == wr) {
            return -EAGAIN;
        }
        *mask = chan->comm_event.value;
    }

    return ENOERR;
}

// ---------------------------------------------------------------------------

static void
serial_xmt_char(serial_channel *chan)
{
    cbuf_t *cbuf = &chan->out_cbuf;
    serial_funs *funs = chan->funs;
    unsigned char c;

    if (chan->support_flow_control) {
        if (chan->flow_desc.flowctrl_software) {
            // if we are required to send an XON/XOFF char, send it before
            // anything else
            // FIXME: what if XON gets corrupted in transit to the other end?
            // Should we resend XON even though the other end may not be
            // wanting
            // to send us stuff at this point?
            if (chan->config.flags & NUM_SERIAL_FLOW_XONXOFF_RX) {
                if (chan->flow_desc.xchar) {
                    if ((funs->putc)(chan, chan->flow_desc.xchar)) {
                        chan->flow_desc.xchar = '\0';
                    }
                    else {  // otherwise there's no space and we have to wait
                        return;
                    }
                }
            }
        }

        // if we're meant to be throttled, just stop and leave
        if (chan->flow_desc.flags & SERIAL_FLOW_OUT_THROTTLED) {
            (funs->stop_xmit)(chan);  // Stop transmitting for now
            return;
        }
    }

    if (!cbuf_empty(cbuf)) {
        cbuf_get(cbuf, &c);
        if ((funs->putc)(chan, c)) {
            cbuf_de(cbuf);
        }
    }
    else { // whether it died in interrupt time?
        (funs->stop_xmit)(chan);  // Done with transmit

        if ((chan->comm_event.waiting & NUM_EV_TXEMPTY) &&
            (chan->comm_event.mask & NUM_EV_TXEMPTY)) {
            chan->comm_event.value = NUM_EV_TXEMPTY;
            chan->comm_event.waiting &= ~NUM_EV_TXEMPTY;
            _drv_event_notify_by_isr(&chan->comm_event.wait);
        }

        // must signal waiters, and wake up selecters for the case when
        // this was the last char to be sent and they hadn't been signalled
        // before (e.g. because of flow control)
        if (cbuf->waiting) {
            cbuf->waiting = false;
            _drv_event_notify_by_isr(&cbuf->wait);
        }
    }
}

// ---------------------------------------------------------------------------

static void
serial_rcv_char(serial_channel *chan, unsigned char c)
{
    cbuf_t *cbuf = &chan->in_cbuf;

    if (chan->support_flow_control) {
        if (chan->flow_desc.flowctrl_software) {

            // for software flow control, if the driver returns one of the
            // characters
            // we act on it and then drop it (the app must not see it)
            if (chan->config.flags & NUM_SERIAL_FLOW_XONXOFF_TX) {
                if (c == DAT_IO_SERIAL_FLOW_CONTROL_XOFF_CHAR) {
                    throttle_tx(chan);
                    return; // it wasn't a "real" character
                }
                else if (c == DAT_IO_SERIAL_FLOW_CONTROL_XON_CHAR) {
                    restart_tx(chan);
                    return; // it wasn't a "real" character
                }
            }
        }

        // If we've hit the high water mark, tell the other side to stop
        if (cbuf_nb(cbuf) >= cbuf->high_water) {
            throttle_rx(chan, false);
        }
    }

    // If the flow control is not enabled/sufficient and the buffer is
    // already full, just throw new characters away.

    if (!cbuf_full(cbuf)) {
        cbuf_en(cbuf, c);
    } // note trailing else
    else if (chan->support_line_status) {
        // Overrun. Report the error.
        serial_line_status_t stat;
        stat.which = NUM_SERIAL_STATUS_OVERRUNERR;
        serial_indicate_status(chan, &stat);
    }

    if ((chan->comm_event.waiting & NUM_EV_RXCHAR) &&
        (chan->comm_event.mask & NUM_EV_RXCHAR)) {
        chan->comm_event.value = NUM_EV_RXCHAR;
        chan->comm_event.waiting &= ~NUM_EV_RXCHAR;
        _drv_event_notify_by_isr(&chan->comm_event.wait);
    }

    if (cbuf->waiting) {
        cbuf->waiting = false;
        _drv_event_notify_by_isr(&cbuf->wait);
    }
}

//----------------------------------------------------------------------------
// Flow control indication callback

static void
serial_indicate_status(serial_channel *chan, serial_line_status_t *s)
{
    if (NUM_SERIAL_STATUS_FLOW == s->which) {
        if (s->value) {
            restart_tx(chan);
        }
        else {
            throttle_tx(chan);
        }
    }

    if (s->which == NUM_SERIAL_STATUS_OVERRUNERR ||
        s->which == NUM_SERIAL_STATUS_PARITYERR ||
        s->which == NUM_SERIAL_STATUS_FRAMEERR) {
        if ((chan->comm_event.waiting & NUM_EV_ERR) &&
            (chan->comm_event.mask & NUM_EV_ERR)) {
            chan->comm_event.value = NUM_EV_ERR;
            chan->comm_event.waiting &= ~NUM_EV_ERR;
            _drv_event_notify_by_isr(&chan->comm_event.wait);
        }
    }
    else if (s->which == NUM_SERIAL_STATUS_BREAK) {
        if ((chan->comm_event.waiting & NUM_EV_BREAK) &&
            (chan->comm_event.mask & NUM_EV_BREAK)) {
            chan->comm_event.value = NUM_EV_BREAK;
            chan->comm_event.waiting &= ~NUM_EV_BREAK;
            _drv_event_notify_by_isr(&chan->comm_event.wait);
        }
    }
    else if (s->which == NUM_SERIAL_STATUS_FLOW) {
        // BUGBUG: couldn't differentiate CTS & DSR
        if (chan->config.flags & NUM_SERIAL_FLOW_DSRDTR_TX) {
            if ((chan->comm_event.waiting & NUM_EV_DSR) &&
                (chan->comm_event.mask & NUM_EV_DSR)) {
                chan->comm_event.value = NUM_EV_DSR;
                chan->comm_event.waiting &= ~NUM_EV_DSR;
                _drv_event_notify_by_isr(&chan->comm_event.wait);
            }
        }
        if (chan->config.flags & NUM_SERIAL_FLOW_RTSCTS_TX) {
            if ((chan->comm_event.waiting & NUM_EV_CTS) &&
                (chan->comm_event.mask & NUM_EV_CTS)) {
                chan->comm_event.value = NUM_EV_CTS;
                chan->comm_event.waiting &= ~NUM_EV_CTS;
                _drv_event_notify_by_isr(&chan->comm_event.wait);
            }
        }
    }
    else if (s->which == NUM_SERIAL_STATUS_RINGINDICATOR) {
        if ((chan->comm_event.waiting & NUM_EV_RING) &&
            (chan->comm_event.mask & NUM_EV_RING)) {
            chan->comm_event.value = NUM_EV_RING;
            chan->comm_event.waiting &= ~NUM_EV_RING;
            _drv_event_notify_by_isr(&chan->comm_event.wait);
        }
    }

    if (chan->status_callback) {
        (*chan->status_callback)(s, chan->status_callback_priv);
    }
}

//----------------------------------------------------------------------------
// Block transfer functions. Not all drivers require these. Those that
// do must follow the required semantics:
//
// Attempt to transfer as much via the block transfer function as
// possible, _but_ if that fails, do the remaining bytes via the
// single-char function. That ensures that all policy decisions can be
// made in this driver, and not in the device driver.
//
// Note: if the driver uses DMA for transmission, an initial failing
// call to the xmt_req function must cause the start_xmit function to
// fall-back to regular CPU-interrupt based single-character
// transmission.

static rcv_req_reply_t
serial_data_rcv_req(serial_channel *chan, int avail,
    int* space_avail, unsigned char** space)
{
    cbuf_t *cbuf = &chan->in_cbuf;
    int gap;

    if (chan->support_flow_control && chan->flow_desc.flowctrl_software) {

        // When there is software flow-control, force the serial device
        // driver to use the single-char xmt/rcv functions, since these
        // have to make policy decision based on the data. Rcv function
        // may also have to transmit data to throttle the xmitter.
        if (chan->config.flags &
            (NUM_SERIAL_FLOW_XONXOFF_TX|NUM_SERIAL_FLOW_XONXOFF_RX)) {
            return (RCV_DISABLED);
        }
    }

    // Check for space
    gap = cbuf_nb(cbuf);
    if (gap == cbuf->len) {
        return (RCV_FULL);
    }

    if (0 == gap) {
        // Buffer is empty. Reset put/get indexes to get max transfer in
        // one chunk.
        cbuf->get = 0;
        cbuf->put = 0;
        gap = cbuf->len;
    }
    else {
        // Free space (G = get, P = put, x = data, . = empty)
        //  positive: xxxxP.....Gxxx
        //  negative: ..GxxxxxP.....        [offer last chunk only]

        // First try for a gap between put and get locations
        gap = cbuf->get - cbuf->put;
        if (gap < 0) {
            // If failed, the gap is between put and the end of buffer
            gap = cbuf->len - cbuf->put;
        }
    }

    if (avail < gap) { gap = avail; } // bound by what's available from hw

    *space_avail = gap;
    *space = &cbuf->data[cbuf->put];

    SERIAL_ASSERT((gap + cbuf_nb(cbuf)) <= cbuf->len, "Buffer will overflow");
    SERIAL_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr");
    SERIAL_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr");

    return (RCV_OK);
}

static void
serial_data_rcv_done(serial_channel *chan, int chars_rcvd)
{
    cbuf_t *cbuf = &chan->in_cbuf;

    cbuf->put += chars_rcvd;

    if (cbuf->put == cbuf->len) { cbuf->put = 0; }

    SERIAL_ASSERT(!cbuf_full(cbuf), "Buffer overflow");
    SERIAL_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr");
    SERIAL_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr");

    if (cbuf->waiting) {
        cbuf->waiting = false;
        _drv_event_notify(&cbuf->wait);
    }

    if (chan->support_flow_control) {
        // If we've hit the high water mark, tell the other side to stop
        if (cbuf_nb(cbuf) >= cbuf->high_water) {
            throttle_rx(chan, false);
        }
    }
}

static xmt_req_reply_t
serial_data_xmt_req(serial_channel *chan, int space,
    int* chars_avail, unsigned char** chars)
{
    cbuf_t *cbuf = &chan->out_cbuf;
    int avail;

    if (chan->support_flow_control && chan->flow_desc.flowctrl_software) {
        // When there is software flow-control, force the serial device
        // driver to use the single-char xmt/rcv functions, since these
        // have to make policy decision based on the data. Rcv function
        // may also have to transmit data to throttle the xmitter.
        if (chan->config.flags &
            (NUM_SERIAL_FLOW_XONXOFF_TX | NUM_SERIAL_FLOW_XONXOFF_RX)) {
            return (XMT_DISABLED);
        }
    }

    // Available data (G = get, P = put, x = data, . = empty)
    //  0:        no data
    //  negative: xxxxP.....Gxxx        [offer last chunk only]
    //  positive: ..GxxxxxP.....
    if (cbuf_empty(cbuf)) {
        return (XMT_EMPTY);
    }

    if (cbuf->get >= cbuf->put) {
        avail = cbuf->len - cbuf->get;
    }
    else {
        avail = cbuf->put - cbuf->get;
    }

    if (avail > space) { avail = space; }   // bound by space in hardware

    *chars_avail = avail;
    *chars = &cbuf->data[cbuf->get];

    SERIAL_ASSERT(avail <= cbuf->len, "Avail overflow");
    SERIAL_ASSERT(!cbuf_full(cbuf), "Buffer overflow");
    SERIAL_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr");
    SERIAL_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr");

    return (XMT_OK);
}

static void
serial_data_xmt_done(serial_channel *chan, int chars_sent)
{
    cbuf_t *cbuf = &chan->out_cbuf;
    serial_funs *funs = chan->funs;
    int space;

    cbuf->get += chars_sent;

    if (cbuf->get == cbuf->len) { cbuf->get = 0; }

    SERIAL_ASSERT(!cbuf_full(cbuf), "Buffer overflow");
    SERIAL_ASSERT(!cbuf_empty(cbuf), "Buffer underflow");
    SERIAL_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr");
    SERIAL_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr");

    if (cbuf_empty(cbuf)) {
        (funs->stop_xmit)(chan);  // Done with transmit
        cbuf->get = cbuf->put = 0; // reset ptrs if empty
    }

    // See if there is now enough room to restart writer
    space = cbuf->len - cbuf_nb(cbuf);
    if (space >= cbuf->low_water) {
        if (cbuf->waiting) {
            cbuf->waiting = false;
            _drv_event_notify(&cbuf->wait);
        }
    }
}

// ---------------------------------------------------------------------------

// EOF control.cpp
